﻿namespace System.ServiceModel.DomainServices.Client
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Globalization;
    using System.Linq;
    using System.Runtime.Serialization;

    [DataContract]
    public abstract class ComplexObject : INotifyPropertyChanged, IEditableObject, INotifyDataErrorInfo
    {
        private EditSession _editSession;
        private bool _isDeserializing;
        private System.ServiceModel.DomainServices.Client.MetaType _metaType;
        private Action _onDataMemberChanged;
        private Action _onDataMemberChanging;
        private Action<string, IEnumerable<ValidationResult>> _onMemberValidationChanged;
        private object _parent;
        private string _parentPropertyName;
        private Dictionary<string, ComplexObject> _trackedInstances;
        private ComplexObjectValidationResultCollection _validationErrors;

        event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        event PropertyChangedEventHandler PropertyChanged;

        protected ComplexObject()
        {
        }

        internal void Attach(object parent, string propertyName, Action onDataMemberChanging, Action onDataMemberChanged, Action<string, IEnumerable<ValidationResult>> onMemberValidationChanged)
        {
            if (parent == null)
            {
                throw new ArgumentNullException("parent");
            }
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentNullException("propertyName");
            }
            if (onDataMemberChanging == null)
            {
                throw new ArgumentNullException("onDataMemberChanging");
            }
            if (onDataMemberChanged == null)
            {
                throw new ArgumentNullException("onDataMemberChanged");
            }
            if (onMemberValidationChanged == null)
            {
                throw new ArgumentNullException("onMemberValidationChanged");
            }
            this.CheckForCycles(parent);
            this._parent = parent;
            this._parentPropertyName = propertyName;
            this._onDataMemberChanging = onDataMemberChanging;
            this._onDataMemberChanged = onDataMemberChanged;
            this._onMemberValidationChanged = onMemberValidationChanged;
        }

        private void AttachComplexObjectInstance(MetaMember metaMember)
        {
            string name = metaMember.Member.Name;
            ComplexObject obj2 = null;
            if (this.TrackedInstances.TryGetValue(name, out obj2))
            {
                obj2.Detach();
                this.TrackedInstances.Remove(name);
            }
            ComplexObject obj3 = (ComplexObject) metaMember.GetValue(this);
            if (obj3 != null)
            {
                obj3.Attach(this, name, new Action(this.OnDataMemberChanging), new Action(this.OnDataMemberChanged), new Action<string, IEnumerable<ValidationResult>>(this.OnMemberValidationChanged));
                this.TrackedInstances[name] = obj3;
                if (obj3.HasValidationErrors)
                {
                    foreach (ValidationResult result in ValidationUtilities.ApplyMemberPath(obj3.ValidationErrors, name))
                    {
                        this.ValidationResultCollection.Add(result);
                    }
                }
            }
        }

        protected void BeginEdit()
        {
            if (!this.IsEditing)
            {
                this._editSession = EditSession.Begin(this);
            }
        }

        protected void CancelEdit()
        {
            if (this.IsEditing)
            {
                this._editSession.Cancel();
                this._editSession = null;
            }
        }

        private void CheckForCycles(object candidateParent)
        {
            ComplexObject obj3;
            for (object obj2 = candidateParent; obj2 != null; obj2 = obj3._parent)
            {
                if (obj2 == this)
                {
                    throw new InvalidOperationException(Resource.CyclicReferenceError);
                }
                obj3 = obj2 as ComplexObject;
                if (obj3 == null)
                {
                    break;
                }
            }
        }

        private ValidationContext CreateValidationContext()
        {
            ValidationContext parentContext = null;
            System.ServiceModel.DomainServices.Client.Entity entity = this.Entity;
            if (((entity != null) && (entity.EntitySet != null)) && (entity.EntitySet.EntityContainer != null))
            {
                parentContext = entity.EntitySet.EntityContainer.ValidationContext;
            }
            return ValidationUtilities.CreateValidationContext(this, parentContext);
        }

        internal void Detach()
        {
            this._parent = null;
            this._parentPropertyName = null;
            this._onDataMemberChanging = null;
            this._onDataMemberChanged = null;
            this._onMemberValidationChanged = null;
        }

        protected void EndEdit()
        {
            if (this.IsEditing)
            {
                List<ValidationResult> validationResults = new List<ValidationResult>();
                ValidationUtilities.TryValidateObject(this, this.CreateValidationContext(), validationResults);
                ValidationUtilities.ApplyValidationErrors(this, validationResults);
                this.NotifyParentMemberValidationChanged(null, validationResults);
                this._editSession = null;
            }
        }

        private void NotifyParentMemberValidationChanged(string propertyName, IEnumerable<ValidationResult> validationResults)
        {
            if (this.IsAttached)
            {
                IEnumerable<ValidationResult> enumerable = ValidationUtilities.ApplyMemberPath(validationResults, this._parentPropertyName);
                string str = this._parentPropertyName;
                if (!string.IsNullOrEmpty(propertyName))
                {
                    str = str + "." + propertyName;
                }
                this._onMemberValidationChanged(str, enumerable);
            }
        }

        private void OnDataMemberChanged()
        {
            if (this.IsAttached)
            {
                this._onDataMemberChanged();
            }
        }

        private void OnDataMemberChanging()
        {
            if (this.IsEditing)
            {
                this._editSession.OnDataMemberUpdate();
            }
            if (this.IsAttached)
            {
                this._onDataMemberChanging();
            }
        }

        [EditorBrowsable(EditorBrowsableState.Never), OnDeserialized]
        public void OnDeserialized(StreamingContext context)
        {
            this._isDeserializing = false;
        }

        [OnDeserializing, EditorBrowsable(EditorBrowsableState.Never)]
        public void OnDeserializing(StreamingContext context)
        {
            this._isDeserializing = true;
        }

        private void OnMemberValidationChanged(string propertyName, IEnumerable<ValidationResult> validationResults)
        {
            this.ValidationResultCollection.ReplaceErrors(propertyName, validationResults);
            this.NotifyParentMemberValidationChanged(propertyName, validationResults);
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (e == null)
            {
                throw new ArgumentNullException("e");
            }
            if (this._propChangedHandler != null)
            {
                this._propChangedHandler(this, e);
            }
        }

        protected void RaiseDataMemberChanged(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentNullException("propertyName");
            }
            if (this.MetaType.HasComplexMembers)
            {
                MetaMember metaMember = this.MetaType[propertyName];
                if (((metaMember != null) && metaMember.IsComplex) && !metaMember.IsCollection)
                {
                    this.AttachComplexObjectInstance(metaMember);
                }
            }
            this.OnDataMemberChanged();
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        protected void RaiseDataMemberChanging(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentNullException("propertyName");
            }
            this.OnDataMemberChanging();
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentNullException("propertyName");
            }
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        private void RaiseValidationErrorsChanged(string propertyName)
        {
            if (this._errorsChangedHandler != null)
            {
                this._errorsChangedHandler(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }

        void IEditableObject.BeginEdit()
        {
            this.BeginEdit();
        }

        void IEditableObject.CancelEdit()
        {
            this.CancelEdit();
        }

        void IEditableObject.EndEdit()
        {
            this.EndEdit();
        }

        IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
        {
            IEnumerable<ValidationResult> enumerable;
            Func<ValidationResult, bool> func = null;
            Func<ValidationResult, bool> func2 = null;
            if (string.IsNullOrEmpty(propertyName))
            {
                if (func == null)
                {
                    func = delegate (ValidationResult e) {
                        if (e.MemberNames.Any<string>())
                        {
                            return e.MemberNames.Contains<string>(propertyName);
                        }
                        return true;
                    };
                }
                enumerable = Enumerable.Where<ValidationResult>(this.ValidationResultCollection, func);
            }
            else
            {
                if (func2 == null)
                {
                    func2 = e => e.MemberNames.Contains<string>(propertyName);
                }
                enumerable = Enumerable.Where<ValidationResult>(this.ValidationResultCollection, func2);
            }
            return enumerable.ToArray<ValidationResult>();
        }

        protected virtual void ValidateProperty(ValidationContext validationContext, object value)
        {
            if (validationContext == null)
            {
                throw new ArgumentNullException("validationContext");
            }
            List<ValidationResult> validationResults = new List<ValidationResult>();
            Validator.TryValidateProperty(value, validationContext, validationResults);
            this.OnMemberValidationChanged(validationContext.MemberName, validationResults);
        }

        protected void ValidateProperty(string propertyName, object value)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentNullException("propertyName");
            }
            System.ServiceModel.DomainServices.Client.Entity entity = this.Entity;
            if (!this.IsDeserializing && ((entity == null) || (!entity.IsDeserializing && !entity.IsApplyingState)))
            {
                if (entity != null)
                {
                    if ((entity.EntitySet != null) && (entity.EntityState != EntityState.New))
                    {
                        entity.EntitySet.EnsureEditable(EntitySetOperations.Edit);
                    }
                    if (entity.IsReadOnly)
                    {
                        throw new InvalidOperationException(Resource.Entity_ReadOnly);
                    }
                }
                MetaMember member = this.MetaType[propertyName];
                if (member == null)
                {
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.Property_Does_Not_Exist, new object[] { this.MetaType.Type.Name, propertyName }), "propertyName");
                }
                if ((((entity != null) && (member.EditableAttribute != null)) && !member.EditableAttribute.AllowEdit) && (!member.EditableAttribute.AllowInitialValue || ((entity.EntityState != EntityState.Detached) && (entity.EntityState != EntityState.New))))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.Property_Is_ReadOnly, new object[] { propertyName }));
                }
                ComplexObject obj2 = value as ComplexObject;
                if ((obj2 != null) && obj2.IsAttached)
                {
                    throw new InvalidOperationException(Resource.ComplexType_InstancesCannotBeShared);
                }
                if (this.MetaType.RequiresValidation)
                {
                    ValidationContext validationContext = this.CreateValidationContext();
                    validationContext.MemberName = propertyName;
                    this.ValidateProperty(validationContext, value);
                }
            }
        }

        internal void VerifyNotEditing()
        {
            if (this.IsEditing)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.Entity_UncommittedChanges, new object[] { this.Entity }));
            }
            foreach (ComplexObject obj2 in this.TrackedInstances.Values)
            {
                obj2.VerifyNotEditing();
            }
        }

        private System.ServiceModel.DomainServices.Client.Entity Entity
        {
            get
            {
                for (object obj2 = this._parent; obj2 != null; obj2 = ((ComplexObject) obj2)._parent)
                {
                    System.ServiceModel.DomainServices.Client.Entity entity = obj2 as System.ServiceModel.DomainServices.Client.Entity;
                    if (entity != null)
                    {
                        return entity;
                    }
                }
                return null;
            }
        }

        [Display(AutoGenerateField=false)]
        public bool HasValidationErrors
        {
            get
            {
                return this.ValidationErrors.Any<ValidationResult>();
            }
        }

        internal bool IsAttached
        {
            get
            {
                return (this._parent != null);
            }
        }

        protected internal bool IsDeserializing
        {
            get
            {
                return this._isDeserializing;
            }
        }

        internal bool IsEditing
        {
            get
            {
                return (this._editSession != null);
            }
        }

        internal System.ServiceModel.DomainServices.Client.MetaType MetaType
        {
            get
            {
                if (this._metaType == null)
                {
                    this._metaType = System.ServiceModel.DomainServices.Client.MetaType.GetMetaType(base.GetType());
                }
                return this._metaType;
            }
        }

        bool INotifyDataErrorInfo.HasErrors
        {
            get
            {
                return (this.ValidationResultCollection.Count > 0);
            }
        }

        private Dictionary<string, ComplexObject> TrackedInstances
        {
            get
            {
                if (this._trackedInstances == null)
                {
                    this._trackedInstances = new Dictionary<string, ComplexObject>();
                }
                return this._trackedInstances;
            }
        }

        [Display(AutoGenerateField=false)]
        public ICollection<ValidationResult> ValidationErrors
        {
            get
            {
                if (this._validationErrors == null)
                {
                    this._validationErrors = new ComplexObjectValidationResultCollection(this);
                }
                return this._validationErrors;
            }
        }

        internal System.ServiceModel.DomainServices.Client.ValidationResultCollection ValidationResultCollection
        {
            get
            {
                return (System.ServiceModel.DomainServices.Client.ValidationResultCollection) this.ValidationErrors;
            }
        }

        private class ComplexObjectValidationResultCollection : ValidationResultCollection
        {
            private ComplexObject _complexObject;

            public ComplexObjectValidationResultCollection(ComplexObject complexObject) : base(complexObject)
            {
                this._complexObject = complexObject;
            }

            private static ICollection<ValidationResult> GetValidationResults(object parent)
            {
                ComplexObject obj2 = parent as ComplexObject;
                if (obj2 != null)
                {
                    return obj2.ValidationErrors;
                }
                return ((Entity) parent).ValidationErrors;
            }

            protected override void OnAdd(ValidationResult item)
            {
                if (this._complexObject._parent != null)
                {
                    item = ValidationUtilities.ApplyMemberPath(item, this._complexObject._parentPropertyName);
                    GetValidationResults(this._complexObject._parent).Add(item);
                }
            }

            protected override void OnClear()
            {
                base.OnClear();
                this._complexObject.NotifyParentMemberValidationChanged(null, Enumerable.Empty<ValidationResult>());
            }

            protected override void OnCollectionChanged()
            {
                this._complexObject.RaisePropertyChanged("ValidationErrors");
            }

            protected override void OnHasErrorsChanged()
            {
                this._complexObject.RaisePropertyChanged("HasValidationErrors");
            }

            protected override void OnPropertyErrorsChanged(string propertyName)
            {
                this._complexObject.RaiseValidationErrorsChanged(propertyName);
            }

            protected override void OnRemove(ValidationResult item)
            {
                if (this._complexObject._parent != null)
                {
                    item = ValidationUtilities.ApplyMemberPath(item, this._complexObject._parentPropertyName);
                    ICollection<ValidationResult> validationResults = GetValidationResults(this._complexObject._parent);
                    ValidationResultEqualityComparer comparer = new ValidationResultEqualityComparer();
                    item = Enumerable.FirstOrDefault<ValidationResult>(validationResults, (Func<ValidationResult, bool>) (p => comparer.Equals(p, item)));
                    if (item != null)
                    {
                        validationResults.Remove(item);
                    }
                }
            }
        }

        private class EditSession
        {
            private ComplexObject _instance;
            private IDictionary<string, object> _snapshot;
            private ValidationResult[] _validationErrors;

            private EditSession(ComplexObject instance)
            {
                this._instance = instance;
                this._validationErrors = instance.ValidationErrors.ToArray<ValidationResult>();
            }

            public static ComplexObject.EditSession Begin(ComplexObject instance)
            {
                return new ComplexObject.EditSession(instance);
            }

            public void Cancel()
            {
                if (this._snapshot != null)
                {
                    ObjectStateUtility.ApplyState(this._instance, this._snapshot);
                }
                ValidationUtilities.ApplyValidationErrors(this._instance, this._validationErrors);
                this._instance.NotifyParentMemberValidationChanged(null, this._validationErrors);
            }

            public void OnDataMemberUpdate()
            {
                if (this._snapshot == null)
                {
                    this._snapshot = ObjectStateUtility.ExtractState(this._instance);
                }
            }
        }
    }
}

